#include <stdlib.h>
#include <ncurses/curses.h>
#include "debugger.h"
#include "display.h"
#include <string.h>

DebuggerPtr new_debugger(CpuPtr cpu)
{
	DebuggerPtr debugger = (DebuggerPtr) malloc(sizeof(DebuggerStr));
	debugger->cpu = cpu;
	debugger->memory = cpu->memory;
	debugger->reg_file = cpu->reg_file;
	debugger->mem_start = 0;
	return debugger;
}

void debugger_initialize(DebuggerPtr debugger)
{
	int height, debugger_height;
	int width, debugger_width;
	int x, debugger_x;
	int y, debugger_y;
	int i;

	// Get sizes and debugger window
	debugger->debug_win = display_get_debugger();

	debugger_x = getbegx(debugger->debug_win);
	debugger_y = getbegy(debugger->debug_win);
	debugger_width = getmaxx(debugger->debug_win) - debugger_x + 1;
	debugger_height = getmaxy(debugger->debug_win) - debugger_y + 1;

	// Initialize memory window
	x = debugger_x + debugger_width - 18;
	y = debugger_y + 2;
	width = 18;
	height = debugger_height - 2;

	wmove(debugger->debug_win, 0, x - debugger_x);
	wattron(debugger->debug_win, A_BOLD);
	wprintw(debugger->debug_win, "Memory:");
	wattroff(debugger->debug_win, A_BOLD);
	wmove(debugger->debug_win, 1, x - debugger_x);
	wprintw(debugger->debug_win, "Addr    Data");

	debugger->memory_win = newwin(height, width, y, x);
	wrefresh(debugger->memory_win);

	// Initialize register windows
	x = debugger_x;
	y = debugger_y + 2;
	width = 13;
	height = 4;

	wmove(debugger->debug_win, 0, 0);
	wattron(debugger->debug_win, A_BOLD);
	wprintw(debugger->debug_win, "General Purpose Registers:");
	wattroff(debugger->debug_win, A_BOLD);

	for (i = 0; i < 4; i++)
	{
		wmove(debugger->debug_win, 1, x - debugger_x);
		wprintw(debugger->debug_win, "Reg  Data");
		wrefresh(debugger->debug_win);

		debugger->register_win[i] = newwin(height, width, y, x);
		wrefresh(debugger->register_win[i]);

		x += 14;
	}

	// Initialize special purpose registers and status work diagram
	x = debugger_x;
	y = debugger_y + 9;
	width = 12;
	height = 3;
	int swdiag_xoffset = 16;
	debugger->swdiag_win = newwin(1, 31, y + 1, swdiag_xoffset + 1 + debugger_x);
	wrefresh(debugger->swdiag_win);

	wmove(debugger->debug_win, y - 2 - debugger_y, 0);
	wattron(debugger->debug_win, A_BOLD);
	wprintw(debugger->debug_win, "Special Purpose Registers:");
	wattroff(debugger->debug_win, A_BOLD);
	wmove(debugger->debug_win, y - 1 - debugger_y, 0);
	wprintw(debugger->debug_win, "Reg  Data");
	wmove(debugger->debug_win, y - 1 - debugger_y, swdiag_xoffset);
	wprintw(debugger->debug_win, "Status Word Diagram");
	wmove(debugger->debug_win, y - debugger_y, swdiag_xoffset);
	wprintw(debugger->debug_win, "|F E D C B A 9 8 7 6 5 4 3 2 1 0|");
	wmove(debugger->debug_win, y + 1 - debugger_y, swdiag_xoffset);
	wprintw(debugger->debug_win, "[                               ]");
	wmove(debugger->debug_win, y + 2 - debugger_y, swdiag_xoffset);
	wprintw(debugger->debug_win, "|N Z C O|reserve|   IRQ lines   |");

	debugger->specreg_win = newwin(height, width, y, x);
	wrefresh(debugger->specreg_win);


	// Display static actions area
	int static_offset = 13;
	wmove(debugger->debug_win, static_offset + 0, 0);
	wattron(debugger->debug_win, A_BOLD);
	wprintw(debugger->debug_win, "Debug Control:");
	wattroff(debugger->debug_win, A_BOLD);
	wmove(debugger->debug_win, static_offset + 1, 0);
	wprintw(debugger->debug_win, "  F1) Load Program         F4) Change Memory Display");
	wmove(debugger->debug_win, static_offset + 2, 0);
	wprintw(debugger->debug_win, "  F2) Run Program          F5) Clear keyboard buffer");
	wmove(debugger->debug_win, static_offset + 3, 0);
	wprintw(debugger->debug_win, "  F3) Step Program         ESC) Exit Debugger");
	wmove(debugger->debug_win, static_offset + 4, 0);
	wattron(debugger->debug_win, A_BOLD);
	wprintw(debugger->debug_win, "  Other keys are sent to keyboard buffer.");
	wattroff(debugger->debug_win, A_BOLD);
	wrefresh(debugger->debug_win);

//	debugger->kbd_win = newwin(3, 65, 15, debugger_x);
//	wmove(debugger->debug_win, 13, 0);
//	wattron(debugger->debug_win, A_BOLD);
//	wprintw(debugger->debug_win, "Keyboard Debug:");
//	wattroff(debugger->debug_win, A_BOLD);
//	wrefresh(debugger->debug_win);
	curs_set(0);
}

void debugger_main_loop(DebuggerPtr debugger)
{
	Bit exit_bit = 0;
	int command = 0;

	debugger_initialize(debugger);
	cpu_initialize(debugger->cpu);

	while(exit_bit == 0)
	{
		debugger_update(debugger);

		if (cpu_get_stop_bit(debugger->cpu))
			timeout(-1);

		command = getch();

		if (command == ERR)
			command = KEY_F(2);

		switch (command)
		{
			case KEY_F(1):
				debugger_load_program(debugger);
				timeout(-1);
				break;

			case KEY_F(2):
				debugger_step_program(debugger);
				timeout(10);
				break;

			case KEY_F(3):
				debugger_step_program(debugger);
				timeout(-1);
				break;

			case KEY_F(4):
				debugger_show_mem(debugger);
				timeout(-1);
				break;

			case KEY_F(5):
				kbdctl_initialize(debugger->cpu->ioctl->kbd);
				timeout(-1);
				break;

			case 0x1B:
				exit_bit = 1;
				timeout(-1);
				break;

			default:
				ioctl_kbdkeypress(debugger->cpu->ioctl, command);
				break;
		}
	}
}

//Load Program this will prompt for a filename
void debugger_load_program(DebuggerPtr debugger) {
	char filename[FILENAME_MAX];
	int fields;
	int error;
	int instructions = 0;
	FILE *file;
	RegisterStr write_address = 0;
	RegisterStr value;
	RegisterPtr val_ptr = &value;


	int debugger_x = getbegx(debugger->debug_win);
	int debugger_y = getbegy(debugger->debug_win);
	int debugger_width = getmaxx(debugger->debug_win) - debugger_x + 1;

	WINDOW *temp_outer_window = newwin(5, debugger_width - 1, debugger_y, debugger_x);
	box(temp_outer_window, 0, 0);
	wrefresh(temp_outer_window);

	WINDOW *temp_inner_window = newwin(3, debugger_width - 3, debugger_y + 1, debugger_x + 1);
	wrefresh(temp_inner_window);

	echo();

	wmove(temp_inner_window, 0, 0);
	wprintw(temp_inner_window, "Enter the name of the program file to load:");
	wmove(temp_inner_window, 1, 0);
	wrefresh(temp_inner_window);
	curs_set(1);

	int bad = 1;
	while (bad == 1)
	{
		bad = 0;
		wmove(temp_inner_window, 0, 0);
		wprintw(temp_inner_window, "Enter the name of the program file to load, or just press enter to cancel.");
		wmove(temp_inner_window, 1, 0);
		wclrtoeol(temp_inner_window);
		wrefresh(temp_inner_window);

		error = wgetstr(temp_inner_window, filename);

		if (error == ERR)
		{
			wclrtoeol(temp_inner_window);
			wprintw(temp_inner_window, "Error in getstr.");
			wrefresh(temp_inner_window);
			bad = 1;
		} else if (strlen(filename) < 1){
			curs_set(0);
			noecho();
			delwin(temp_inner_window);
			delwin(temp_outer_window);
			redrawwin(debugger->debug_win);
			wrefresh(debugger->debug_win);
			return;
		} else {
			file = fopen(filename, "r");
			if (file == NULL)
			{
				wclrtoeol(temp_inner_window);
				wprintw(temp_inner_window, "Error opening file for reading.");
				wrefresh(temp_inner_window);
				bad = 1;
			} else {
				fields = fscanf(file, "%4hx\n", val_ptr);
				if (fields < 1)
				{
					wclrtoeol(temp_inner_window);
					wprintw(temp_inner_window, "Error reading initial offset from file.");
					wrefresh(temp_inner_window);
					bad = 1;
				}
			}
		}
	}

	debugger->mem_start = value;
	write_address = value;
	cpu_set_pc(debugger->cpu, value);

	while (fscanf(file, "%4hx\n", val_ptr) > 0)
	{
		instructions++;

		Memory_setMemoryAddressRegister(debugger->memory, write_address++);
		Memory_setMemoryDataRegister(debugger->memory, (value & 0xff00) >> 8);
		Memory_performWrite(debugger->memory);

		Memory_setMemoryAddressRegister(debugger->memory, write_address++);
		Memory_setMemoryDataRegister(debugger->memory, value);
		Memory_performWrite(debugger->memory);
	}
	cpu_reset_stop_Bit(debugger->cpu);

	fclose(file);
	curs_set(0);
	noecho();
	wprintw(temp_inner_window, "Read %d instructions into memory. Press any key to continue", instructions);
	wmove(temp_inner_window, 3, 0);
	wclear(display_get_console());
	wrefresh(display_get_console());
	wrefresh(temp_inner_window);
	getch();
	delwin(temp_inner_window);
	delwin(temp_outer_window);
	redrawwin(debugger->debug_win);
	wrefresh(debugger->debug_win);
}

//Step Program runs the next step of the program.
void debugger_step_program(DebuggerPtr debugger){
	if (!cpu_get_stop_bit(debugger->cpu))
		cpu_step(debugger->cpu);
}

void print_0x(WINDOW *window)
{
	wattron(window, A_DIM);
	wprintw(window, "0x");
	wattroff(window, A_DIM);
}

void update_memory(DebuggerPtr debugger)
{
	Byte byte_a, byte_b;
	int i;

	for (i = 0; i < 16; i++)
	{
		Memory_setMemoryAddressRegister(debugger->memory, debugger->mem_start + (2 * i));
		Memory_performRead(debugger->memory);
		byte_a = Memory_getMemoryDataRegister(debugger->memory);
		Memory_setMemoryAddressRegister(debugger->memory, debugger->mem_start + (2 * i) + 1);
		Memory_performRead(debugger->memory);
		byte_b = Memory_getMemoryDataRegister(debugger->memory);

		wmove(debugger->memory_win, i, 0);
		print_0x(debugger->memory_win);
		wprintw(debugger->memory_win, "%04X: ", debugger->mem_start + (2 * i));

		print_0x(debugger->memory_win);
		wprintw(debugger->memory_win, "%02X ", byte_a);

		print_0x(debugger->memory_win);
		wprintw(debugger->memory_win, "%02X", byte_b);
	}

	wrefresh(debugger->memory_win);
}

void update_registers(DebuggerPtr debugger)
{
	RegisterStr value;
	int i;
	int win;

	for (i = 0; i < 16; i++)
	{
		win = i / 4;
		value = RegisterFile_getRegisterWord(debugger->reg_file, i);

		wmove(debugger->register_win[win], i % 4, 0);
		wprintw(debugger->register_win[win], "$R%1X: ", i);

		print_0x(debugger->register_win[win]);
		wprintw(debugger->register_win[win], "%04X", value);

	}

	for (i = 0; i < 4; i++)
		wrefresh(debugger->register_win[i]);
}

void update_sp_registers(DebuggerPtr debugger)
{
	RegisterStr pc = cpu_get_pc(debugger->cpu);
	RegisterStr ir = cpu_get_ir(debugger->cpu);
	RegisterStr sw = cpu_get_sw(debugger->cpu);

	wmove(debugger->specreg_win, 0, 0);
	wprintw(debugger->specreg_win, "PC:  ");
	print_0x(debugger->specreg_win);
	wprintw(debugger->specreg_win, "%04X", pc);

	wmove(debugger->specreg_win, 1, 0);
	wprintw(debugger->specreg_win, "IR:  ");
	print_0x(debugger->specreg_win);
	wprintw(debugger->specreg_win, "%04X", ir);

	wmove(debugger->specreg_win, 2, 0);
	wprintw(debugger->specreg_win, "SW:  ");
	print_0x(debugger->specreg_win);
	wprintw(debugger->specreg_win, "%04X", sw);

	wrefresh(debugger->specreg_win);
}

void update_swdiag(DebuggerPtr debugger)
{
	int i;
	RegisterStr sw = cpu_get_sw(debugger->cpu);


	for (i = 0; i < 16; i++)
	{
		int x = (15 - i) * 2;
		wmove(debugger->swdiag_win, 0, x);

		Bit value = (sw & (1 << i)) >> i;
		wprintw(debugger->swdiag_win, "%d", value);
	}


	wrefresh(debugger->swdiag_win);
}

void update_kbd(DebuggerPtr debugger)
{
	KbdctlPtr kbd = debugger->cpu->ioctl->kbd;
	int presses = (kbd->write_position - kbd->read_position + BUFFER_LENGTH) % BUFFER_LENGTH;
	wmove(debugger->kbd_win, 0, 0);
	wclrtoeol(debugger->kbd_win);
	if (kbd->buffer_full)
	{
		presses = BUFFER_LENGTH;
		wattron(debugger->kbd_win, A_BOLD);
		wprintw(debugger->kbd_win, "    **KEYBOARD BUFFER FULL**   ");
		wattroff(debugger->kbd_win, A_BOLD);
		wprintw(debugger->kbd_win, "%2d keypresses buffered", BUFFER_LENGTH);
	} else {
		wprintw(debugger->kbd_win, "      Buffer Contents          %2d keypresses buffered", (kbd->write_position + BUFFER_LENGTH - kbd->read_position) % BUFFER_LENGTH);
	}

	wmove(debugger->kbd_win, 1, 0);
	wclrtoeol(debugger->kbd_win);
	wprintw(debugger->kbd_win, "Hex :");

	int i;
	for (i = 0; i < presses; i++)
	{
		wprintw(debugger->kbd_win, " %02X", kbd->buffer[(i + kbd->read_position) % BUFFER_LENGTH]);
	}

	wmove(debugger->kbd_win, 2, 0);
	wclrtoeol(debugger->kbd_win);
	wprintw(debugger->kbd_win, "Char:");
	for (i = 0; i < presses; i++)
	{
		if ((kbd->buffer[(i + kbd->read_position) % BUFFER_LENGTH] > 32) && (kbd->buffer[(i + kbd->read_position) % BUFFER_LENGTH] < 127))
			wprintw(debugger->kbd_win, "  %c", kbd->buffer[(i + kbd->read_position) % BUFFER_LENGTH]);
		else
			wprintw(debugger->kbd_win, "   ");
	}


	wrefresh(debugger->kbd_win);
}

//refresh the screen
void debugger_update(DebuggerPtr debugger){
	update_memory(debugger);
	update_registers(debugger);
	update_sp_registers(debugger);
	update_swdiag(debugger);
//	update_kbd(debugger);
}


void debugger_show_mem(DebuggerPtr debugger)
{

	int debugger_x = getbegx(debugger->debug_win);
	int debugger_y = getbegy(debugger->debug_win);
	int debugger_width = getmaxx(debugger->debug_win) - debugger_x + 1;


	WINDOW *temp_outer_window = newwin(4, debugger_width - 1, debugger_y, debugger_x);
	box(temp_outer_window, 0, 0);
	wrefresh(temp_outer_window);

	WINDOW *temp_inner_window = newwin(2, debugger_width - 3, debugger_y + 1, debugger_x + 1);
	wrefresh(temp_inner_window);

	echo();
	curs_set(1);


	int bad = 1;
	int mem_val;
	char str[6];
	while (bad == 1)
	{
		bad = 0;
		wmove(temp_inner_window, 0, 0);
		wclrtoeol(temp_inner_window);
		wprintw(temp_inner_window, "Enter to cancel, or enter memory location to begin display at: 0x");
		wrefresh(temp_inner_window);
		wgetstr(temp_inner_window, str);
		if (strlen(str) == 4)
		{
			mem_val = (int) strtol(str, NULL, 16);
			if (mem_val < 0xFFFF && mem_val > 0x0000)
			{
				debugger->mem_start = mem_val;
			} else {
				bad = 1;
				wmove(temp_inner_window, 1, 0);
				wclrtoeol(temp_inner_window);
				wprintw(temp_inner_window, "Bad value, outside allowed memory range.", mem_val);
				wrefresh(temp_inner_window);
			}
		} else if (strlen(str) == 0){
			noecho();
			curs_set(0);

			delwin(temp_inner_window);
			delwin(temp_outer_window);
			redrawwin(debugger->debug_win);
			wrefresh(debugger->debug_win);
			return;
		} else {
			bad = 1;
			wmove(temp_inner_window, 1, 0);
			wclrtoeol(temp_inner_window);
			wprintw(temp_inner_window, "Value wrong length, please enter a 4 digit Hexadecimal value to begin display at.", mem_val);
			wrefresh(temp_inner_window);
		}
	}


	wmove(temp_inner_window, 1, 0);
	wclrtoeol(temp_inner_window);
	wprintw(temp_inner_window, "\"0x%04X\" is the new memory address to begin display at. Press any key to continue", mem_val);
	wrefresh(temp_inner_window);
	noecho();
	curs_set(0);
	getch(); //press any key to continue

	delwin(temp_inner_window);
	delwin(temp_outer_window);
	redrawwin(debugger->debug_win);
	wrefresh(debugger->debug_win);
}
